Skip to content

perf(agc): remember an evicting import request's budget refusal until it could succeed - #6730

Merged
oneandonlydean merged 20 commits into
boykopovar:mainfrom
EagleBearerYS:perf/agc-remember-evicting-refusals
Oct 19, 2026
Merged

oneandonlydean merged 20 commits into
boykopovar:mainfrom
EagleBearerYS:perf/agc-remember-evicting-refusals

Conversation

@EagleBearerYS

@EagleBearerYS EagleBearerYS commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

What

At a full host import budget, a request that may evict asked again on every use. That covers an upload's region that needs an import, and HostImportFor. Each time it walked every live import in evictableBytes and was refused again. #6457 remembers the refusals of requests that may not evict. This PR remembers the evicting ones too, for exactly as long as the refusal can't turn into a success.

Depends on: #6457. This branch is #6457 plus this PR's commits and a merge of main 5d9dcc4.

Why. In Resident Evil Village (PPSA01556)'s first scene the 6400 MiB budget is full, and one upload range is refused at every use:

[gpu] host import budget: 6399 MiB live of 6400 MiB (APS5_HOST_IMPORT_MIB); 5978411 imports (634831 MiB) refused so far, 5315222 of them remembered, last 0x2372400000+0x80000 by build
  • The line came up in 126 of that run's budget reports.
  • In a profiled run of the same scene there were 37,627 refusals per present.
  • Import lookups took 710 ms per present, against 12 ms in a run that held the range.
  • With ~4,400 live imports, almost all of that time is the walk.

How. Without a held overlapping import, an evicting request is refused when the bytes of non-evictable imports outside its run, plus the run's own bytes, exceed the budget. A refusal can only become a success when one of these happens:

  • that sum drops: an import is retired, or one becomes evictable;
  • the run, the budget or the import window changes;
  • a request for a smaller range of the same block can step down.

So each refusal is remembered with the state that decided it and the room it left, for every block a step-down tries. It is answered from memory, and still counted as refused and remembered, while all of these hold:

  • epoch and liveBytes are unchanged: no import was made or retired;
  • the budget hasn't grown. A smaller budget can only keep it refused, which is perf(agc): take the host import budget from the device's memory budget #6479's stillRefused rule;
  • the import window is unchanged;
  • the run's ranges are the same objects (a request for any range of the refused block is answered);
  • no import outside the run could have become evictable since. The refusal's own walk records, for free, the first use count at which each non-evictable import could pass the 256-use protection. Held imports are kept apart, and the memory ends at whichever comes first.

A remembered refusal reproduces the scan's own decision: if the run can step down and the request's range fits in the room left, less any budget drop since, it steps down as the scan would, without a new walk; otherwise it is refused. A join's refusal is remembered too, kept apart from other requests for the same run, since a join skips the held-import check and never steps down.

Every condition is exact or errs toward trying again, so a request that could succeed is never answered from memory.

Not changed:

Cost in Resident Evil Village's scene (by design, not measured yet): at ~72 address-based builds per second, which restamp the imports they map, the memory is walked again at most once per build, instead of once per request.

Overlap. At this head the overlap check lists the same other PRs as for #6457, so this PR adds no conflict beyond #6457's own. #6479, also stacked on #6457, changes the same budget path in importRunAt and adds tests to BdaResources.cpp. This PR uses #6479's rule for a budget that moves, so whichever lands second keeps both. #6918 (Scramblrrrr, draft) clips a partly committed import window to the aligned committed span that holds the request, in windowRun, and merges cleanly with this PR. The evicting refusals this PR remembers are keyed by the run windowRun returns and compared by its ranges, so a clipped window is remembered as its own run, and a window whose committed span changes is a new request.

Tested

  • Linux (Ubuntu 24.04, GCC 13.3, CI's configure flags, lavapipe; the tests use agc_driver_graphics' mock device):
    • Red, on perf(agc): import adjacent host ranges as aligned blocks and stop re-trying budget refusals #6457's GuestBufferMemory.cpp with the tests kept: 8 evicting requests for one refused piece at a full budget with nothing evictable scanned the imports 8 times, not once. Green: Graphics validation tests passed.
    • The new tests ×10: 10/10.
    • ctest -R '^agc' ×5: 605/605 three times, and twice 604/605. The one failure was agc_driver_preparation_scope_settle, a known load flake that passes 10/10 alone.
    • Each condition of the memory, weakened in turn, fails its own test: a retire, a budget raise, aging out, a raised build floor, the containing range, the import window, and imports no build held.
  • GitHub's runners (run 38869228735; "before" is perf(agc): import adjacent host ranges as aligned blocks and stop re-trying budget refusals #6457's head with the new tests, "after" is this branch, agc_driver_graphics included):
    • Linux: before fails, after passes 10/10. The libc and libkernel suites pass.
    • Windows: before fails, after passes 10/10. In the suites only guest_kqueue fails, on both sides; it fails on that runner with main too.
  • The review follow-ups (Linux, lavapipe), each red with the previous commit's GuestBufferMemory.cpp:
    • any range of a refused block: 8 evicting requests alternating between two 64 KiB ranges of one refused 256 KiB block scanned the imports 8 times, not once;
    • joins: 8 uploads bound across three refused ranges scanned the imports for their join 8 times, not once;
    • step-downs: once an address-based build was refused a 256 KiB block and imported its 128 KiB half, every evicting request for the other half scanned again: 8 evicting requests that step down from a refused block scanned the imports 8 times, not once.
    • Each test then grows the budget and checks the request now imports. agc_driver_graphics ×10: 10/10; ctest -R '^agc': 607/607 (43 skipped).
    • On GitHub's runners (run), with the previous head's GuestBufferMemory.cpp and the new tests, agc_driver_graphics fails in every one of 3 runs on Linux and Windows; with the three commits it passes 10 of 10 on both. In the suites only guest_kqueue fails on Windows, on both sides.
  • The merge of main: ctest -R '^agc' on GitHub's Linux runner at the merged head passes 606 of 607; the one failure is the same agc_driver_preparation_scope_settle load flake.
  • MinGW -fsyntax-only -Wall: no new diagnostics against perf(agc): import adjacent host ranges as aligned blocks and stop re-trying budget refusals #6457's head.
  • AMD Radeon 780M (RDNA 3, AMD driver 25.10.20.01), Windows 11 (WinLibs GCC 15.2.0, Release), run by keravyn-ops at 7539ba6, this branch merged with main 52798f7 (comment): all 63 tests built from the targets this PR changes pass.
  • python3 tools/check_conventions.py --base origin/main: passed. No comment lines added.

Checklist

At a full host import budget with nothing evictable, a request that may
evict tried again on every use, and each try walked every live import to
add up what could be evicted. In a title's first scene at its 6400 MiB
budget that was 37,627 refusals per present, each one a walk over about
4,400 imports, while the address-based builds kept every import held.

Such a request now remembers its final refusal, the one with no smaller
block left to try. Later requests for the same run, from the same range,
are answered from that memory, still counted as refused and remembered,
until an import is made or retired, the budget or the import window
changes, the run's ranges change, or an import could have become
evictable. An import becomes evictable only once the use count passes
its last use by the 256-use protection, and a held one also needs a
higher build floor, so the walk records the first such use among the
imports it could not evict, held ones apart: the memory survives a new
build while no held import can age out yet. Requests that may not evict
keep their behaviour.
The remembered refusal of a request that may evict expired on any budget
change. A smaller budget can only keep the request refused, and boykopovar#6479
moves the budget in 64 MiB steps once a second, so the memory now ends
only when the budget grows, as boykopovar#6479's stillRefused does.
An evicting refusal was remembered for the range holding the requested
address and dropped by a request for another range of the same run, so
requests landing in different ranges of one refused block, as uploads
and accesses across a block of adjacent ranges do, scanned the imports
every time. The memory now keeps the room the refusal left and answers
any range of the run that is larger than it: such a range cannot step
down to a block of its own either. A smaller range still recomputes,
and a run that cannot step down at all is answered for every range.
A join's budget refusal was never remembered, so a buffer bound across
ranges that the budget could not take tried the join, with a scan of
every import, at each upload's pre-pass. A join now remembers its final
refusal like any other evicting request, under the same conditions. The
memory keeps whether it was a join and answers only the same kind of
request, since a join skips the held-import check and never steps down.
An evicting request whose block is refused but leaves room for its own
range steps down to a smaller block, and only the last block it tried
was remembered. Once an address-based build had stepped down inside a
block, every later evicting request for the rest of that block scanned
every import at the block's level before it reached the memory below.

A refusal is now remembered before the request steps down, with the
room it left. A remembered refusal answers with the same decision the
scan would make: while the memory is current only a lower budget can
change that room, so the request steps down to the same smaller block
when its range fits in the room, and is refused otherwise.
@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 7849 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp L1659
PR 7978 docs/dev/debt/libSceAgcDriver.md L196-197

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 1191 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 1324 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 2434 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
docs/dev/debt/libSceAgcDriver.md
PR 5021 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 5058 docs/dev/debt/libSceAgcDriver.md
PR 5499 docs/dev/debt/libSceAgcDriver.md
PR 5925 docs/dev/debt/libSceAgcDriver.md
PR 6177 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6185 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6186 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6194 docs/dev/debt/libSceAgcDriver.md
PR 6197 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 6223 docs/dev/debt/libSceAgcDriver.md
PR 6273 docs/dev/debt/libSceAgcDriver.md
PR 6280 docs/dev/debt/libSceAgcDriver.md
PR 6292 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6300 docs/dev/debt/libSceAgcDriver.md
PR 6412 core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6481 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6548 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6549 docs/dev/debt/libSceAgcDriver.md
PR 6612 docs/dev/debt/libSceAgcDriver.md
PR 6615 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6628 core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 6727 docs/dev/debt/libSceAgcDriver.md
PR 6808 docs/dev/debt/libSceAgcDriver.md
PR 6867 docs/dev/debt/libSceAgcDriver.md
PR 6868 docs/dev/debt/libSceAgcDriver.md
PR 6947 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6951 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 6959 docs/dev/debt/libSceAgcDriver.md
PR 6977 docs/dev/debt/libSceAgcDriver.md
PR 7085 docs/dev/debt/libSceAgcDriver.md
PR 7201 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 7233 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7256 docs/dev/debt/libSceAgcDriver.md
PR 7292 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 7332 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7449 docs/dev/debt/libSceAgcDriver.md
PR 7457 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7473 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7505 docs/dev/debt/libSceAgcDriver.md
PR 7514 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7533 docs/dev/debt/libSceAgcDriver.md
PR 7604 docs/dev/debt/libSceAgcDriver.md
PR 7800 docs/dev/debt/libSceAgcDriver.md
PR 7844 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7927 docs/dev/debt/libSceAgcDriver.md
PR 8014 docs/dev/debt/libSceAgcDriver.md
PR 8099 docs/dev/debt/libSceAgcDriver.md

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 16, 2026
@parl0v

parl0v commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Data point from a CPU-bound scene on Windows: RTX 3060 12 GB (NVIDIA 617.42), i5-12400F, Windows 10 19045, MinGW GCC Release. Minecraft Dungeons II (PPSA16064 01.100.100), title screen, no input, 200 s rows, APS5_PROFILE_DRAW=1 APS5_PROFILE_GPU=1; main c842491 vs main + this PR, interleaved with three other PRs in one batch, two rounds. FPS is the mean of the last 10 [present] windows, GPU busy and idle gaps per frame over the last 8. This scene is CPU-bound on this machine (45-48 ms of idle gaps per frame).

libs FPS GPU busy idle gaps
main 6.60 / 6.60 105 / 104 ms 46 / 48 ms
+ this PR (efdba5d) 6.68 / 6.60 (+0.6 %) 104 / 106 ms 46 / 45 ms

Within the run-to-run spread (about ±1.5 % here), no regression, 0 fatal. The title screen on a 12 GB card has no evicting imports to refuse, so it doesn't exercise the path this changes.

AI-assisted: yes (Claude Code)

EagleBearerYS added a commit to EagleBearerYS/AnyPS5 that referenced this pull request Oct 17, 2026
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 17, 2026
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 17, 2026
@keravyn-ops

Copy link
Copy Markdown
Contributor

AMD / Windows run of this PR at 7539ba6: Radeon 780M (RDNA 3, 0x15bf), AMD driver 25.10.20.01 (32.0.21020.1007), Windows 11 Home 10.0.26100, WinLibs GCC 15.2.0 r7, Release.

Every ctest entry built from the test targets this PR changes passes (63):

agc_recorder_device_selection, agc_pipeline_cache, agc_pipeline_cache_file, agc_pipeline_library, agc_pipeline_library_off, agc_pipeline_library_fast_only, agc_import_watch, agc_import_watch_other_family, agc_unchanged_cpu_stamps, agc_storage_kept_unit_settle, agc_storage_baseline_settled, agc_storage_integer_clear, agc_storage_upload_lifetime, agc_storage_upload_failure, agc_storage_written_buffer_order, agc_storage_split_writeback, agc_copied_direct_ordering, agc_storage_writeback_blocks, agc_recorder_read_list, agc_space_pending_writes, agc_host_import_unmap, agc_parked_import_unmap, agc_host_import_retirement, agc_sparse_run_import, agc_sparse_run_import_copied, agc_unit_shadow, agc_storage_cpu_snapshot, agc_descriptor_pool_recycling, agc_draw_snapshot_cap, agc_draw_snapshot_epoch, agc_multisample_guest_access, agc_draw_inputs_in_place, agc_pending_write_snapshot, agc_resident_present, agc_queued_write_groups, agc_tiled_resolve, agc_slice_dirty, agc_metadata_pass, agc_host_import_second_mapping, agc_pending_key_stores, agc_shadowed_key_stores, agc_sample_dumps, agc_driver_vertex_target_range_copied, agc_driver_vertex_target_range_imported, agc_driver_undithered_alpha_to_coverage_state, agc_driver_msaa_dcc_state, agc_driver_alpha_to_coverage_state, agc_driver_prepared_graphics_prebuild_unset, agc_driver_prepared_graphics_prebuild_0, agc_driver_prepared_graphics_prebuild_1, agc_driver_graphics, agc_driver_f16_fma_denormal_modes, agc_driver_linear_target_resident, agc_driver_prepared_graphics, agc_driver_prepared_unregistered_graphics, agc_driver_storage_write_back_overhang, agc_sparse_buffer_holes, agc_pipeline_journal, agc_pipeline_library_clear_waits_for_build, agc_descriptor_pool_reset_failure, agc_driver_address_space_teardown, agc_compute_descriptor_layout_flags, agc_driver_covered_centroid_state

AI-assisted: yes (runs made on the device above).

…mber-evicting-refusals

boykopovar#6457's branch now carries main's per-device import settles. Its
ImportSettle sits where this branch adds EvictingRefusal; keep both.
@github-actions github-actions Bot added needs rebase Conflicts with main, rebase needed and removed needs rebase Conflicts with main, rebase needed labels Oct 18, 2026
@oneandonlydean oneandonlydean self-assigned this Oct 18, 2026
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 18, 2026
@github-actions github-actions Bot added perf numbers missing perf pull request without a before and after summary line unblocks title Names a title ID on the Unblocks line labels Oct 18, 2026
@github-actions

Copy link
Copy Markdown

This pull request is labeled perf, so it needs the summary line of a capture on main and one with the change, from the same title, scene and machine, as the Performance report form asks. Please paste both aps5-summary.jsonl lines, attach the two traces and add the output of tools/trace_digest.py.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 18, 2026
Resolves the AGC driver debt file bullet by bullet.
@github-actions github-actions Bot added needs rebase Conflicts with main, rebase needed and removed needs rebase Conflicts with main, rebase needed labels Oct 19, 2026
Main's sameRange for a failed import now takes the import budget, as the HostImport overload does, so a recorded failure is compared against the run the budget would import again.
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 19, 2026
The host-import debt entry takes boykopovar#6472's budget sentence and keeps this branch's sentence on remembered refusals.
@github-actions github-actions Bot added needs rebase Conflicts with main, rebase needed and removed needs rebase Conflicts with main, rebase needed labels Oct 19, 2026
# Conflicts:
#	core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
#	docs/dev/debt/libSceAgcDriver.md
@github-actions github-actions Bot added needs rebase Conflicts with main, rebase needed and removed needs rebase Conflicts with main, rebase needed labels Oct 19, 2026
# Conflicts:
#	docs/dev/debt/libSceAgcDriver.md
@github-actions github-actions Bot added needs rebase Conflicts with main, rebase needed and removed needs rebase Conflicts with main, rebase needed labels Oct 19, 2026
x
# Conflicts:
#	docs/dev/debt/libSceAgcDriver.md
@github-actions github-actions Bot added needs rebase Conflicts with main, rebase needed and removed needs rebase Conflicts with main, rebase needed labels Oct 19, 2026
# Conflicts:
#	core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
@github-actions github-actions Bot added needs rebase Conflicts with main, rebase needed and removed needs rebase Conflicts with main, rebase needed labels Oct 19, 2026
# Conflicts:
#	core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
#	core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
#	docs/dev/debt/libSceAgcDriver.md
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 19, 2026
@oneandonlydean
oneandonlydean merged commit 3aa35dc into boykopovar:main Oct 19, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* build docs perf numbers missing perf pull request without a before and after summary line perf Performance change unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants